package life.cqq.jwt.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import life.cqq.jwt.enums.TokenStatus;
import org.springframework.lang.Nullable;

import javax.crypto.spec.SecretKeySpec;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 优化后的最终版本的工具类，以后项目中使用这个类即可！
 * 优化后的最终版本的工具类，以后项目中使用这个类即可！
 * 优化后的最终版本的工具类，以后项目中使用这个类即可！
 * 优化后的最终版本的工具类，以后项目中使用这个类即可！
 * 优化后的最终版本的工具类，以后项目中使用这个类即可！
 * 优化后的最终版本的工具类，以后项目中使用这个类即可！
 * 优化后的最终版本的工具类，以后项目中使用这个类即可！
 * 优化后的最终版本的工具类，以后项目中使用这个类即可！
 * 优化后的最终版本的工具类，以后项目中使用这个类即可！
 * 优化后的最终版本的工具类，以后项目中使用这个类即可！
 * 优化后的最终版本的工具类，以后项目中使用这个类即可！
 * 优化后的最终版本的工具类，以后项目中使用这个类即可！
 * 优化后的最终版本的工具类，以后项目中使用这个类即可！
 * 优化后的最终版本的工具类，以后项目中使用这个类即可！
 */

/**
 * Created by QQ.Cong on 2022-12-30 / 15:35:15
 *
 * @author: CongQingquan
 * @Description: JWT工具类(针对于JWT规范下的JWS类别)
 */
public class FinalJwsUtils {

    /**
     * Sign
     *
     * @param header             元数据
     * @param payload            载体
     * @param signatureAlgorithm 签名算法
     * @param secretKey          签名密匙（对于不同的签名算法有不同的意义）
     * @param duration           有效时长
     * @return
     */
    public static String sign(@Nullable Map<String, Object> header,
                              @Nullable Map<String, Object> payload,
                              SignatureAlgorithm signatureAlgorithm,
                              String secretKey,
                              long duration) {
        if (header == null) {
            header = new HashMap<>();
        }
        header.put("typ", "JWT");
        header.put("alg", signatureAlgorithm.getJcaName());
        return Jwts.builder()
                .setHeader(header)
                .setClaims(payload)
                .setExpiration(new Date(System.currentTimeMillis() + duration))
                .signWith(new SecretKeySpec(secretKey.getBytes(), signatureAlgorithm.getJcaName()))
                .compact();
    }

    /**
     * Parse token
     *
     * @param token                   需要解析的 token
     * @param signatureAlgorithm      签名算法
     * @param secretKey               签名密匙
     * @param allowedClockSkewSeconds 允许偏差的时间戳
     * @return
     */
    public static Jwt<Header, Claims> parse(String token,
                                            SignatureAlgorithm signatureAlgorithm,
                                            String secretKey,
                                            long allowedClockSkewSeconds) {
        return Jwts.parserBuilder()
                .setSigningKey(new SecretKeySpec(secretKey.getBytes(), signatureAlgorithm.getJcaName()))
                .setAllowedClockSkewSeconds(allowedClockSkewSeconds)
                .build()
                .parse(token);
    }

    /**
     * Get token status
     *
     * @param token              需要解析的 token
     * @param signatureAlgorithm 签名算法
     * @param secretKey          签名密匙
     * @return
     */
    public static TokenStatus getTokenStatus(String token, SignatureAlgorithm signatureAlgorithm, String secretKey) {
        try {
            parse(token, signatureAlgorithm, secretKey, 0L);
        } catch (ExpiredJwtException exception) {
            return TokenStatus.EXPIRED;
        } catch (Exception exception) {
            return TokenStatus.INVALID;
        }
        return TokenStatus.VALID;
    }

    /**
     * Token is valid (Parsable and unexpired)
     *
     * @param token              需要解析的 token
     * @param signatureAlgorithm 签名算法
     * @param secretKey          签名密匙
     */
    public static boolean valid(String token, SignatureAlgorithm signatureAlgorithm, String secretKey) {
        return getTokenStatus(token, signatureAlgorithm, secretKey) == TokenStatus.VALID;
    }

    /**
     * Refresh token (Must call valid function before calling refresh function)
     *
     * @param token              需要刷新的 token
     * @param signatureAlgorithm 签名算法
     * @param secretKey          签名密匙（对于不同的签名算法有不同的意义）
     * @param duration           有效时长
     * @return
     */
    public static String refresh(String token, SignatureAlgorithm signatureAlgorithm, String secretKey, long duration) {
        if (!valid(token, signatureAlgorithm, secretKey)) {
            throw new IllegalArgumentException("Expired token");
        }
        Jwt<Header, Claims> parseRes = parse(token, signatureAlgorithm, secretKey, 5L);
        return sign(parseRes.getHeader(), parseRes.getBody(), signatureAlgorithm, secretKey, duration);
    }

    public static void main(String[] args) throws InterruptedException {
        getTokenStatusTest();
        refreshTest();
    }

    static void getTokenStatusTest() {
        Map<String, Object> header = new HashMap<>();
        header.put("header1", "h1");
        header.put("header2", "h2");

        Map<String, Object> payload = new HashMap<>();
        payload.put("payload1", "p1");
        payload.put("payload2", "p2");

        String secretKey = "11111111111111111111111111111111";
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long duration = TimeUnit.SECONDS.toMillis(5);

        String token = sign(header, payload, signatureAlgorithm, secretKey, duration);
        Jwt<Header, Claims> parse = parse(token, signatureAlgorithm, secretKey, 0L);
        Long exp = parse.getBody().get("exp", Long.class);
        String format =
                DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss").format(LocalDateTime.ofInstant(Instant.ofEpochSecond(exp),
                        ZoneId.systemDefault()));
        System.out.println("过期时间: " + format);

        try {
            while (true) {
                parse(token, signatureAlgorithm, secretKey, 0L);
                System.out.println("当前时间: " + DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss").format(LocalDateTime.now())
                        + "\t Token 状态: " + getTokenStatus(token, signatureAlgorithm, secretKey).name());
                Thread.sleep(1000);
            }
        } catch (Exception exception) {
            System.out.println("Token 已过期，Token 状态: " + getTokenStatus(token, signatureAlgorithm, secretKey).name());
        }
    }

    static void refreshTest() throws InterruptedException {
        Map<String, Object> header = new HashMap<>();
        header.put("header1", "h1");
        header.put("header2", "h2");

        Map<String, Object> payload = new HashMap<>();
        payload.put("payload1", "p1");
        payload.put("payload2", "p2");

        String secretKey = "11111111111111111111111111111111";
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long duration = TimeUnit.SECONDS.toMillis(5);

        String token = sign(header, payload, signatureAlgorithm, secretKey, duration);
        while (true) {
            Long exp = parse(token, signatureAlgorithm, secretKey, 0L).getBody().get("exp", Long.class);
            System.out.println(
                    "过期时间: " + DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss").format(LocalDateTime.ofInstant(Instant.ofEpochSecond(exp), ZoneId.systemDefault()))
                    + "\t 当前时间: " + DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss").format(LocalDateTime.now()));
            token = refresh(token, signatureAlgorithm, secretKey, duration);
            Thread.sleep(1000);
        }
    }
}